﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using collections = System.Collections;
using Bettzueche.RLGlue.RLTypes;

namespace Bettzueche.RLGlue.Generics {

    /*
    Reward          : double
    Terminal Flag   : int
    Message         : char*
    Task_Spec       : char*
     */

    /// <summary>
    /// RL_abstract_type gemäß der RL-Glue Spezifikation.<para>
    /// Wird in diesem Framework nur intern für Netzwerkkommunikation verwendet.</para>
    /// </summary>
    /// <seealso cref="RL_Abstract_Net_Type"/>
    /// <seealso cref="Action"/>
    /// <seealso cref="Observation"/>
    class RL_abstract_type {
        public int NumInts { get; set; }
        public int NumDoubles { get; set; }
        public int NumChars { get; set; }
        public int[] IntArray { get; set; }
        public double[] DoubleArray { get; set; }
        public char[] CharArray { get; set; }

        /// <summary>
        /// Neues Default Abstract-Type.<para>
        /// Felder sind 0 bzw leere Arrays.</para>
        /// </summary>
        public RL_abstract_type() {
            NumInts = NumDoubles = NumChars = 0;
            IntArray = new int[0];
            DoubleArray = new double[0];
            CharArray = new char[0];
        }
    }

    /// <summary>
    /// 
    /// </summary>
    interface RL_Abstract_Net_Type{}

    /// <summary>
    /// Abstrakter Repräsentant für Werte wie bspw. Aktion oder Zustand.<para>
    /// Die Klasse ist generisch, zur Unterstützung persönlicher Datenrepräsentationen.</para>
    /// </summary>
    /// <typeparam name="T">Typ des Dateninhalts. Sollte int, double oder char sein.</typeparam>
    /// <seealso cref="Observation"/>
    /// <seealso cref="Action"/>
    abstract class RL_Abstract_Net_Type<T> : IEnumerable<T>, IEqualityComparer<RL_Abstract_Net_Type<T>>, RL_Abstract_Net_Type
        where T : struct
    {
        
        #region Properties & Indexer
        
        protected T[] values;
        /// <summary>
        /// Gibt eine Kopie aller Werte als Array.
        /// </summary>
        public virtual T[] Values { 
            get {
                T[] copy = new T[values.Length];
                Array.Copy(values,copy,values.Length);
                return copy;
            }
            protected set {
                values = value;
            }
        }

        /// <summary>
        /// Gets a 32-bit integer that represents the total number of elements in all
        /// the dimensions of the content.
        /// </summary>
        public int Length { get { return values.Length; } protected set { /*nix*/;} }

        /// <summary>
        /// Gibt oder setzt den Zustandswert am spezifizierten Index.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        /// <exception cref="System.IndexOutOfRangeException"></exception>
        public T this[int index] {
            get { return values[index]; }
            set { values[index] = value; }
        }

        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="content"></param>
        public RL_Abstract_Net_Type(params T[] content) {
            this.values = content;
            checkType();
        }

        [System.Diagnostics.Conditional("DEBUG")]
        private void checkType() {
            Type t = typeof(T);
            if (!(t.Equals(typeof(int)) || t.Equals(typeof(double))|| t.Equals(typeof(char))))
                System.Diagnostics.Debug.WriteLine("Content Type of RL_Abstract_Net_Type is not one oft the recommended: int, double, char.", "Warning");
        }

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator() {
            foreach (T item in values) {
                yield return item;
            }
        }

        #endregion
        #region IEnumerable Members

        collections.IEnumerator collections.IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }

        #endregion

        #region Object Overrides

        /// <summary>
        /// Converts the value of this instance to a System.String.
        /// </summary>
        /// <returns>A string with Name and values aof this instance.</returns>
        public override string ToString() {
            String delimiter = ",";
            StringBuilder res = new StringBuilder("RL_Abstract_Net_Type<" + typeof(T).Name + ">[");
            for (int i = 0; i < values.Length-1; i++) {
                res.Append(values[i]).Append(delimiter);
            }
            res.Append(values[values.Length-1]).Append("]");
            return res.ToString();
        }

        public override int GetHashCode() {
            return values.GetHashCode();
        }

        public override bool Equals(object obj) {
            if (obj is RL_Abstract_Net_Type<T>) {
                return Equals((RL_Abstract_Net_Type<T>)obj);
            }
            return false;
        }

        public virtual bool Equals(RL_Abstract_Net_Type<T> other) {
            return values.SequenceEqual(other.values);
        }

        #endregion


        #region IEqualityComparer<RL_Abstract_Net_Type<T>> Members

        public bool Equals(RL_Abstract_Net_Type<T> x, RL_Abstract_Net_Type<T> y) {
            return x.Equals(y);
        }

        public virtual int GetHashCode(RL_Abstract_Net_Type<T> obj) {
            return obj.GetHashCode();
        }

        #endregion


    }

    /// <summary>
    /// Zustand, bzw. Umwelteigenschaft oder Beobachtung.
    /// </summary>
    /// <typeparam name="T">Type sollte einer der primitiven Datentypen int, double oder char sein.</typeparam>
    /// <example>
    /// Dieses Beispiel zeigt die Nutzungsmöglichkeiten von Observation:
    /// <code>
    /// Observation&lt;int&gt; zustandX = new Observation(42);               //Feld Nummer 42
    /// Observation&lt;double&gt; qWerteX = new Observation(1.0,.0,-1.0,.0); //Q-Werte für N,O,S,W in Feld 42
    /// //...
    /// for (int i = 0; i &lt; qWerteX.Length; i++ ) {
    ///     qWerteX[i] += i/2; 
    /// }
    /// Console.WriteLine(qWerteX);
    /// // Output:
    /// // Observation&lt;Double&gt;[1.0,1.0,1.0,1.5]
    /// </code>
    /// </example>
    class Observation<T> : RL_Abstract_Net_Type<T>, IState
        where T : struct {
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="observation"></param>
        public Observation(params T[] observation) : base(observation) {    
        }


        #region Object Overrides

        /// <summary>
        /// Converts the value of this instance to a System.String.
        /// </summary>
        /// <returns>A string with Name and values aof this instance.</returns>
        public override string ToString() {
            String delimiter = ",";
            StringBuilder res = new StringBuilder("Observation<" + typeof(T).Name + ">[");
            for (int i = 0; i < values.Length-1; i++) {
                res.Append(values[i]).Append(delimiter);
            }
            res.Append(values[values.Length-1]).Append("]");
            return res.ToString();
        }

        public override int GetHashCode() {
            return ("Observation<"+typeof(T).Name + ">").GetHashCode() ^ base.GetHashCode();
        }

        public override bool Equals(object obj) {
            if (obj is Observation<T>) {
                return Equals((Observation<T>)obj);
            }
            return false;
        }

        public bool Equals(Observation<T> other) {
            if (other is Observation<T>) {
                return values.SequenceEqual(other.values);
            }
            return false;
        }

        #endregion

        #region Cast Operators

        /// <summary>
        /// Expliziter Cast von Action zu Observation
        /// </summary>
        /// <param name="ac"></param>
        /// <returns>Ein neues Observation mit kopierten Werten aus der zu castenden Action</returns>
        public static explicit operator Observation<T>(Action<T> ac) {
            return new Observation<T>(ac.Values);
        }

        /// <summary>
        /// Explizieter Cast von RL_abstract_Type zu Observation.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        /// <exception cref="InvalidCastException">wenn der generische Typ von Observation nicht int, double oder char</exception>
        public static explicit operator Observation<T>(RL_abstract_type obj) {
            Observation<T> result = new Observation<T>();
            if (typeof(T).Equals(typeof(int))) {
                result.values = (T[])(object)obj.IntArray;
            }
            if (typeof(T).Equals(typeof(double))) {
                result.values = (T[])(object)obj.DoubleArray;
            }
            if (typeof(T).Equals(typeof(char))) {
                result.values = (T[])(object)obj.CharArray;
            }
            else {
                throw new InvalidCastException("Cannot explicit cast, because generic Type of the Observation isn't one of int, double or char.");
            }
            return result;
        }


        public static explicit operator RL_abstract_type(Observation<T> obs) {
            RL_abstract_type result = new RL_abstract_type();
            if (typeof(T).Equals(typeof(int))) {
                result.IntArray = (int[])(object)obs.values;
            }
            if (typeof(T).Equals(typeof(double))) {
                result.DoubleArray = (double[])(object)obs.values;
            }
            if (typeof(T).Equals(typeof(char))) {
                result.CharArray = (char[])(object)obs.values;
            }
            else {
                throw new InvalidCastException("Cannot explicit cast, because generic Type of the Observation isn't one of int, double or char.");
            }
            return result;
        }

        #endregion

    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T">Type sollte einer der primitiven Datentypen int, double oder char sein.</typeparam>
    class Action<T> : RL_Abstract_Net_Type<T>, IAction
        where T : struct {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        public Action(params T[] action) : base(action) {
        }


        #region Object Overrides

        /// <summary>
        /// Converts the value of this instance to a System.String.
        /// </summary>
        /// <returns>A string with Name and values aof this instance.</returns>
        public override string ToString() {
            String delimiter = ",";
            StringBuilder res = new StringBuilder("Action<" + typeof(T).Name + ">[");
            for (int i = 0; i < values.Length-1; i++) {
                res.Append(values[i]).Append(delimiter);
            }
            res.Append(values[values.Length-1]).Append("]");
            return res.ToString();
        }

        public override int GetHashCode() {
            return ("Action<"+typeof(T).Name + ">").GetHashCode() ^ base.GetHashCode();
        }

        public override bool Equals(object obj) {
            if (obj is Action<T>) {
                return Equals((Action<T>)obj);
            }
            return false;
        }

        public bool Equals(Action<T> other) {
            if (other is Observation<T>) {
                return values.SequenceEqual(other.values);
            }
            return false;
        }

        #endregion

        #region Cast Operators

        /// <summary>
        /// Expliziter Cast von Action zu Observation
        /// </summary>
        /// <param name="ac"></param>
        /// <returns>Ein neues Observation mit kopierten Werten aus der zu castenden Action</returns>
        public static explicit operator Action<T>(Observation<T> ac) {
            return new Action<T>(ac.Values);
        }

        /// <summary>
        /// Explizieter Cast von RL_abstract_Type zu Action.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        /// <exception cref="InvalidCastException">wenn der generische Typ von Observation nicht int, double oder char</exception>
        public static explicit operator Action<T>(RL_abstract_type obj) {
            Action<T> result = new Action<T>();
            if (typeof(T).Equals(typeof(int))) {
                result.values = (T[])(object)obj.IntArray;
            }
            if (typeof(T).Equals(typeof(double))) {
                result.values = (T[])(object)obj.DoubleArray;
            }
            if (typeof(T).Equals(typeof(char))) {
                result.values = (T[])(object)obj.CharArray;
            }
            else {
                throw new InvalidCastException("Cannot explicit cast, because generic Type of the Action isn't one of int, double or char.");
            }
            return result;
        }


        public static explicit operator RL_abstract_type(Action<T> act) {
            RL_abstract_type result = new RL_abstract_type();
            if (typeof(T).Equals(typeof(int))) {
                result.IntArray = (int[])(object)act.values;
            }
            if (typeof(T).Equals(typeof(double))) {
                result.DoubleArray = (double[])(object)act.values;
            }
            if (typeof(T).Equals(typeof(char))) {
                result.CharArray = (char[])(object)act.values;
            }
            else {
                throw new InvalidCastException("Cannot explicit cast, because generic Type of the Action isn't one of int, double or char.");
            }
            return result;
        }

        #endregion
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="O">Datentyp der Zustände (Beobachtungen); Sollte int, double oder char sein</typeparam>
    /// <typeparam name="A">Datentyp der Aktionen; Sollte int, double oder char sein</typeparam>
    struct Observation_Action<O,A> 
        where O: struct
        where A: struct
    {
        public readonly Observation<O> Observation;
        public readonly Action<A> Action;

        public Observation_Action (O[] observation, params A[] action)
        {
            Observation = new Observation<O>(observation);
            Action = new Action<A>(action);
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="O">Datentyp der Zustände (Beobachtungen); Sollte int, double oder char sein</typeparam>
    struct  Reward_Observation_Terminal<O>
        where O : struct
    {
        public double Reward;
        public readonly Observation<O> Observation;
        public bool Terminal;

        public Reward_Observation_Terminal(double reward, bool terminal, params O[] observation) {
            Observation = new Observation<O>(observation);
            Terminal = terminal;
            Reward = reward;
        }
    }


    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="O">Datentyp der Zustände (Beobachtungen); Sollte int, double oder char sein</typeparam>
    /// <typeparam name="A">Datentyp der Aktionen; Sollte int, double oder char sein</typeparam>
    struct Reward_Observation_Action_Terminal<O, A>
        where O : struct
        where A : struct
    {
        public double Reward;
        public readonly Observation<O> Observation;
        public readonly Action<A> Action;
        public bool Terminal;

        public Reward_Observation_Action_Terminal(double reward, O[] observation, A[] action, bool terminal) {
            Reward = reward;
            Observation = new Observation<O>(observation);
            Action = new Action<A>(action);
            Terminal = terminal;
        }
    }



}
